<template>
  <div class="form-designer">
    <el-container>
      <el-header v-if="header" class="widget-container-header">
        <design-toolbar :toolbar="toolbar">
          <template #toolbar-left>

            <slot name="toolbar-left">
              <el-dropdown v-if="undoRedo">
                <el-button
                    link
                    type="primary"
                    size="default"
                    icon="el-icon-edit">操作
                  <el-icon class="el-icon--right">
                    <arrow-down/>
                  </el-icon>
                </el-button>

                <template #dropdown>
                  <el-dropdown-menu>
                    <el-dropdown-item
                        divided
                        v-if="undoRedo" :disabled="historySteps.index === 0"
                        @click.native="widgetForm = handleUndo()">撤销
                    </el-dropdown-item>
                    <el-dropdown-item
                        divided
                        v-if="undoRedo" :disabled="historySteps.index === historySteps.steps.length - 1"
                        @click.native="widgetForm = handleRedo()">重做
                    </el-dropdown-item>
                    <el-dropdown-item
                        divided
                        v-if="toolbar.includes('clear')"
                        icon="el-icon-delete"
                        @click.native=" handleClear()"><span>清空</span>
                    </el-dropdown-item>
                    <el-dropdown-item divided v-if="toolbar.includes('import')"
                                      @click.native="$refs.formImportJson.importJson()">导入JSON
                    </el-dropdown-item>

                  </el-dropdown-menu>
                </template>
              </el-dropdown>
              <el-dropdown>
                <el-button
                    link
                    type="primary"
                    size="default"
                    icon="el-icon-search">预览
                  <el-icon class="el-icon--right">
                    <arrow-down/>
                  </el-icon>
                </el-button>
                <template #dropdown>
                  <el-dropdown-menu>
                    <el-dropdown-item @click.native="previewTable()">预览表格</el-dropdown-item>
                    <el-dropdown-item @click.native="previewForm()">预览表单</el-dropdown-item>
                  </el-dropdown-menu>
                </template>
              </el-dropdown>
              <el-dropdown>
                <el-button
                    link
                    type="primary"
                    size="default"
                    icon="el-icon-search">保存
                  <el-icon class="el-icon--right">
                    <arrow-down/>
                  </el-icon>
                </el-button>
                <template #dropdown>
                  <el-dropdown-menu>
                    <el-dropdown-item v-if="toolbar.includes('generate')" @click.native="handleGenerateJson()">
                      生成JSON
                    </el-dropdown-item>
                    <el-dropdown-item icon="el-icon-edit-outline"
                                      v-if="toolbar.includes('save')"
                                      @click.native="saveOption()">保存
                    </el-dropdown-item>
                    <slot name="save-dropdown-item"></slot>
                  </el-dropdown-menu>
                </template>
              </el-dropdown>
            </slot>
          </template>
          <template #toolbar-center>
            <slot name="toolbar-center">
            </slot>
          </template>
          <template #toolbar-right>
            <slot name="toolbar-right"></slot>
            <el-button v-if="toolbar.includes('close')"
                       link
                       type="primary"
                       size="default"
                       @click="$emit('close')"
                       icon="el-icon-close">关闭
            </el-button>
          </template>
        </design-toolbar>
      </el-header>
      <el-container style="height: calc(100vh - 60px) ;border: 1px rgba(0,0,0,0.47) solid">
        <!-- 左侧字段 -->
        <el-aside class="" :width="leftWidth">
          <div class="fields-list">
            <div v-for="(field, index) in fields" :key="index">
              <template v-if="field.list.find(f => includeFields.includes(f.type))">
                <div class="field-title">{{ field.title }}</div>
                <draggable tag="ul" :list="field.list" :group="{ name: 'form', pull: 'clone', put: false }"
                           ghost-class="ghost" :sort="false">
                  <template #item="{element,index}">
                    <li class="field-label"
                        v-if="includeFields.includes(element.type)"
                        :key="index">
                      <a @click="handleFieldClick(element)">
                        <i class="icon iconfont"
                           :class="element.icon"></i>
                        <span>{{ element.title || element.label }}</span>
                      </a>
                    </li>
                  </template>
                </draggable>
              </template>
            </div>
          </div>
        </el-aside>
        <!-- 中间主布局 -->
        <el-container :key="designMainKey" class="widget-container"
                      direction="vertical">

          <el-main :style="{background: widgetForm.column.length === 0 ? `url(${widgetEmpty}) no-repeat 50%`: ''}">
            <widget-form ref="widgetForm"
                         :data="widgetForm"
                         v-model:select="widgetFormSelect"
                         :sql-type-mapper="sqlTypeMapper"
                         @change="handleHistoryChange(widgetForm)"></widget-form>
          </el-main>
        </el-container>
        <!-- 右侧配置 -->
        <el-aside class="widget-config-container"
                  :width="rightWidth">
          <el-tabs v-model="configTab"
                   stretch>
            <el-tab-pane label="字段属性"
                         name="widget"
                         style="padding: 0 10px;">
              <widget-config :data="widgetFormSelect"></widget-config>
            </el-tab-pane>
            <el-tab-pane label="表单属性"
                         name="form"
                         lazy
                         style="padding: 0 10px;">
              <form-config :data="widgetForm"></form-config>
            </el-tab-pane>
          </el-tabs>
        </el-aside>
      </el-container>

      <!-- 生成JSON -->
      <el-drawer title="生成JSON"
                 :visible.sync="generateJsonVisible"
                 size="50%"
                 append-to-body
                 destroy-on-close>
        <monaco-editor v-model="widgetFormPreview"
                       keyIndex="generate"
                       height="82%"
                       :read-only="true"></monaco-editor>
        <div class="drawer-foot">
          <el-popover placement="top"
                      trigger="hover"
                      width="250px">
            <el-form v-model="configOption"
                     style="padding: 0 20px"
                     label-suffix="："
                     label-width="90px"
                     label-position="left">
              <el-form-item label="类型">
                <el-popover placement="top-start"
                            trigger="hover"
                            content="复制json对象"
                            style="margin-right: 15px;">
                  <el-radio slot="reference"
                            v-model="configOption.generateType"
                            value="json">json
                  </el-radio>
                </el-popover>
                <el-popover placement="top-start"
                            trigger="hover"
                            content="复制string字符串，可直接用于后端保存无需再次处理。">
                  <el-radio slot="reference"
                            v-model="configOption.generateType"
                            value="string">string
                  </el-radio>
                </el-popover>
              </el-form-item>
            </el-form>

            <el-button size="default"
                       type="primary"
                       slot="reference"
                       @click="handleGenerate">生成
            </el-button>
          </el-popover>
          <el-button size="default"
                     type="primary"
                     @click="handleCopy"
                     style="margin-left: 10px;">复制
          </el-button>
        </div>
      </el-drawer>
      <!-- 预览 -->
      <form-preview ref="formPreview"></form-preview>
      <form-import-json ref="formImportJson" @success="formImportJsonSuccess"></form-import-json>
      <form-preview-table ref="formPreviewTable"></form-preview-table>
    </el-container>
  </div>
</template>

<script>
import fields from './fieldsConfig.js'
import beautifier from './utils/json-beautifier'
import MonacoEditor from './utils/monaco-editor'
import widgetEmpty from './assets/widget-empty.png'
import history from './mixins/history'

import Draggable from 'vuedraggable'

import WidgetForm from './WidgetForm.vue'
import FormConfig from './FormConfig.vue'
import WidgetConfig from './WidgetConfig.vue'
import optionUtils from "../../option/optionUtils";
import DesignToolbar from "./designToolbar.vue";
import FormPreview from "./formPreview.vue";
import FormImportJson from "./formImportJson.vue";
import FormPreviewTable from "./formPreviewTable.vue";

import key from 'keymaster'
import startMySqlMapper from "./startMySqlMapper";
import startColMapperTools from "./startColMapperTools";
import {Sort} from "@element-plus/icons-vue";

export default {
  name: "FormDesign",
  components: {
    Sort,
    FormPreviewTable,
    FormImportJson,
    FormPreview,
    DesignToolbar,
    Draggable,
    MonacoEditor,
    WidgetForm,
    FormConfig,
    WidgetConfig
  },
  mixins: [history],
  props: {
    header: {
      type: Boolean,
      default: true,
    },
    options: {
      type: [Object, String],
      default: () => {
        return {
          column: []
        }
      },
      dataStorageType: {
        type: String,
        default: ""
      }
    },
    storage: {
      type: Boolean,
      default: false
    },
    asideLeftWidth: {
      type: [String, Number],
      default: '270px'
    },
    asideRightWidth: {
      type: [String, Number],
      default: '380px'
    },
    showGithubStar: {
      type: Boolean,
      default: true
    },
    toolbar: {
      type: Array,
      default: () => {
        return ['import', 'generate', 'preview', 'clear', "save", 'close']
      }
    },
    undoRedo: {
      type: Boolean,
      default: true
    },
    includeFields: {
      type: Array,
      default: () => {
        const arr = []
        fields.forEach(f => {
          f.list.forEach(c => {
            arr.push(c.type)
          })
        })
        return arr
      }
    },
    customFields: {
      type: Array,
    },
    sqlTypeMapper: {
      type: Object,
      default: () => {
        return startMySqlMapper
      }
    }
  },
  provide() {
    return {
      getSqlTypeMapper: () => {
        return this.sqlTypeMapper
      },
    }
  },
  watch: {
    options: {
      handler(val) {
        let options = val
        if (typeof options == 'string') {
          try {
            options = eval('(' + options + ')')
          } catch (e) {
            console.error('非法配置')
            options = {column: []}
          }
        }
        options = startColMapperTools.formOptionToSQL(options, this.sqlTypeMapper)
        this.transAvueOptionsToFormDesigner(options).then(res => {
          this.widgetForm = {...this.widgetForm, ...res}
        })
      },
      deep: true
    }
  },
  computed: {
    leftWidth() {
      if (typeof this.asideLeftWidth == 'string') {
        return this.asideLeftWidth
      } else {
        return `${this.asideLeftWidth}px`
      }
    },
    rightWidth() {
      if (typeof this.asideRightWidth == 'string') {
        return this.asideRightWidth
      } else {
        return `${this.asideRightWidth}px`
      }
    }
  },
  data() {
    return {
      widgetEmpty,
      fields,
      widgetForm: {
        column: [],
        labelPosition: 'left',
        labelSuffix: '：',
        labelWidth: 120,
        gutter: 0,
        menuBtn: true,
        submitBtn: true,
        submitText: '提交',
        emptyBtn: true,
        emptyText: '清空',
        menuPosition: 'center'
      },
      widgetFormPreview: {},
      configTab: 'widget',
      widgetFormSelect: {},
      previewVisible: false,
      generateJsonVisible: false,
      importJsonVisible: false,
      importJson: {},
      widgetModels: {},
      configOption: {
        generateType: 'json'
      },
      history: {
        index: 0, // 当前下标
        maxStep: 20, // 最大记录步数
        steps: [], // 历史步数
      },
      designMainKey: Math.random(),
    }
  },
  mounted() {
    this.handleLoadStorage()
    this.handleLoadCss()
    key.unbind("ctrl+s", "all")
    key('ctrl+s', () => {
      this.saveOption()
      return false;
    });
  },
  beforeUnmount() {
    key.unbind("ctrl+s", "all")
  },
  methods: {
    // 组件初始化时加载本地存储中的options(需开启storage),若不存在则读取用户配置的options
    async handleLoadStorage() {
      let options = this.options
      if (typeof options == 'string') {
        try {
          options = eval('(' + options + ')')
        } catch (e) {
          console.error('非法配置')
          options = {column: []}
        }
      }
      if (!options.column) options.column = []
      this.widgetForm = this.initHistory({
        index: 0,
        maxStep: 20,
        steps: [await this.transAvueOptionsToFormDesigner({...this.widgetForm, ...options})],
        storage: this.storage
      })

      if (this.undoRedo) {
        window.addEventListener('keydown', (evt) => {
          // 监听 cmd + z / ctrl + z 撤销
          if ((evt.metaKey && !evt.shiftKey && evt.keyCode == 90) || (evt.ctrlKey && !evt.shiftKey && evt.keyCode == 90)) {
            this.widgetForm = this.handleUndo()
          }

          // 监听 cmd + shift + z / ctrl + shift + z / ctrl + y 重做
          if ((evt.metaKey && evt.shiftKey && evt.keyCode == 90) || (evt.ctrlKey && evt.shiftKey && evt.keyCode == 90) || (evt.ctrlKey && evt.keyCode == 89)) {
            this.widgetForm = this.handleRedo()
          }
        }, false)
      }

    },
    // 加载icon
    handleLoadCss() {
      const head = document.getElementsByTagName('head')[0]
      const script = document.createElement('link')
      script.rel = 'stylesheet'
      script.type = 'text/css'
      script.href = 'https://at.alicdn.com/t/font_1254447_zc9iezc230c.css'
      head.appendChild(script)
      // this.loadScript('css', 'https://at.alicdn.com/t/font_1254447_zc9iezc230c.css')
    },
    // Avue文档链接
    handleAvueDoc() {
      window.open('https://avuejs.com/doc/form/form-doc', '_blank')
    },

    // 左侧字段点击
    handleFieldClick(item) {
      const activeIndex = this.widgetForm.column.findIndex(c => c.prop === this.widgetFormSelect.prop) + 1
      let newIndex = 0
      if (activeIndex === -1) {
        this.widgetForm.column.push(item)
        newIndex = this.widgetForm.column.length - 1
      } else {
        this.widgetForm.column.splice(activeIndex, 0, item)
        newIndex = activeIndex
      }
      this.$refs.widgetForm.handleWidgetAdd({newIndex})
    },
    // 预览 - 弹窗
    previewForm() {
      if (!this.widgetForm.column || this.widgetForm.column.length < 1) {
        this.$message.error("没有需要展示的内容")
        return
      }
      this.transformToAvueOptions(this.widgetForm).then(data => {
        this.$refs.formPreview.preview(optionUtils.handlerFormConfigVariable(data))
      })
    },
    previewTable() {
      if (!this.widgetForm.column || this.widgetForm.column.length < 1) {
        this.$message.error("没有需要展示的内容")
        return
      }
      this.transformToAvueOptions(this.widgetForm).then(data => {
        this.$refs.formPreviewTable.preview(optionUtils.handlerFormConfigVariable(data))
      })
    },
    formImportJsonSuccess(data) {
      this.widgetForm = data
      this.handleHistoryChange(this.data)
    },

    // 生成JSON - 弹窗
    handleGenerateJson() {
      console.log("生成数据")
      this.transformToAvueOptions(this.widgetForm).then(data => {
        this.widgetFormPreview = data
        this.generateJsonVisible = true
      })
    },
    // 生成JSON - 弹窗 - 确定
    handleGenerate() {
      this.transformToAvueOptions(this.widgetForm).then(data => {
        if (this.configOption.generateType && this.configOption.generateType === 'string') {
          const config = {};
          config.minify = true;
          this.$emit('submit', beautifier(data, config))
        } else {
          this.$emit('submit', data)
        }
      })
    },
    // 生成JSON - 弹窗 - 拷贝
    handleCopy() {
      this.transformToAvueOptions(this.widgetForm).then(data => {
        this.$Clipboard({
          text: beautifier(data, {
            minify: true
          })
        }).then(() => {
          this.$message.success('复制成功')
        }).catch(() => {
          this.$message.error('复制失败')
        })
      })
    },

    // 清空
    handleClear() {
      if (!this.widgetForm || !this.widgetForm.column || this.widgetForm.column.length < 1) {
        this.$message.error("没有需要清空的内容")
        return
      }
      this.$confirm('确定要清空吗？', '警告', {
        type: 'warning'
      }).then(() => {
        let temp = {};
        temp.column = []
        this.widgetForm = temp
        this.widgetModels = {};
        this.widgetFormSelect = {};

        this.handleHistoryChange(this.widgetForm)
        // this.designMainKey = Math.random();
      }).catch(() => {
      })
    },
    saveOption() {
      this.transformToAvueOptions(this.widgetForm).then(data => {
        data = startColMapperTools.formOptionToSQL(data, this.sqlTypeMapper)
        this.$emit("submit", data)
      })
    },
    getFormOption() {
      return this.transformToAvueOptions(this.widgetForm);
    },
    // 表单设计器配置项 转化为 Avue配置项
    transformToAvueOptions(obj) {
      return new Promise((resolve, reject) => {
        try {
          const data = this.deepClone(obj)
          for (let i = 0; i < data.column.length; i++) {
            const col = data.column[i]
            if (col.type === 'dynamic' && col.children && col.children.column && col.children.column.length > 0) {
              const c = col.children.column;
              c.forEach(item => {
                delete item.subfield
              })
              this.transformToAvueOptions(col.children).then(res => {
                col.children = res
              })
            } else if (col.type === 'group') {
              if (!data.group) data.group = []
              const group = {
                label: col.label,
                icon: col.icon,
                prop: col.prop,
                arrow: col.arrow,
                collapse: col.collapse,
                display: col.display
              }
              this.transformToAvueOptions(col.children).then(res => {
                group.column = res.column
                data.group.push(group)
              })
              data.column.splice(i, 1)
              i--
            } else if (['checkbox', 'radio', 'tree', 'cascader', 'select'].includes(col.type)) {
              switch (col.dicOption){
                case   'static':
                  delete col.dicUrl
                  delete col.dicMethod
                  delete col.dicQuery
                  delete col.dicQueryConfig
                  break
                case 'remote':
                  delete col.dicData
                  if (col.dicQueryConfig && col.dicQueryConfig.length > 0) {
                    const query = {}
                    col.dicQueryConfig.forEach(q => {
                      if (q.key && q.value) query[q.key] = q.value
                    })
                    col.dicQuery = query
                    delete col.dicQueryConfig
                  } else delete col.dicQueryConfig

                  break
              }
              delete col.dicOption

            } else if (['upload'].includes(col.type)) {
              if (col.headersConfig && col.headersConfig.length > 0) {
                const headers = {}
                col.headersConfig.forEach(h => {
                  if (h.key && h.value) headers[h.key] = h.value
                })
                col.headers = headers
              } else delete col.headers
              delete col.headersConfig

              if (col.dataConfig && col.dataConfig.length > 0) {
                const data = {}
                col.dataConfig.forEach(h => {
                  if (h.key && h.value) data[h.key] = h.value
                })
                col.data = data
              } else delete col.data
              delete col.dataConfig
            }
            if (col.change) col.change = eval(col.change)
            else delete col.change
            if (col.click) col.click = eval(col.click)
            else delete col.click
            if (col.focus) col.focus = eval(col.focus)
            else delete col.focus
            if (col.blur) col.blur = eval(col.blur)
            else delete col.blur
          }
          resolve(data)
        } catch (e) {
          reject(e)
        }
      })
    },
    // Avue配置项 转化为 表单设计器配置项
    transAvueOptionsToFormDesigner(obj) {
      if (typeof obj == 'string') obj = eval('(' + obj + ')')
      const data = this.deepClone(obj)
      return new Promise((resolve, reject) => {
        try {
          if (data.column && data.column.length > 0) {
            data.column.forEach(col => {
              if (col.type === 'dynamic' && col.children && col.children.column && col.children.column.length > 0) {
                const c = col.children.column;
                c.forEach(item => {
                  item.subfield = true
                })
                this.transAvueOptionsToFormDesigner(col.children).then(res => {
                  col.children = res
                })
              } else if (['checkbox', 'radio', 'tree', 'cascader', 'select'].includes(col.type)) {
                if (!col.dicData && col.dicQuery && typeof col.dicQuery == 'object') {
                  const arr = []
                  for (let key in col.dicQuery) {
                    arr.push({
                      key,
                      value: col.dicQuery[key],
                      $cellEdit: true
                    })
                  }
                  col.dicQueryConfig = arr
                }
                if (col.dicUrl) col.dicOption = 'remote'
                else col.dicOption = 'static'
                if (!col.dicData) col.dicData = []
              } else if (['upload'].includes(col.type)) {
                if (col.headers && typeof col.headers == 'object') {
                  const arr = []
                  for (let key in col.headers) {
                    arr.push({
                      key,
                      value: col.headers[key],
                      $cellEdit: true
                    })
                  }
                  col.headersConfig = arr
                } else col.headersConfig = []

                if (col.data && typeof col.data == 'object') {
                  const arr = []
                  for (let key in col.data) {
                    arr.push({
                      key,
                      value: col.data[key],
                      $cellEdit: true
                    })
                  }
                  col.dataConfig = arr
                } else col.dataConfig = []
              }
            })
          }
          if (data.group && data.group.length > 0) {
            for (let i = 0; i < data.group.length; i++) {
              if (!data.column) data.column = []
              const col = data.group[i]

              const group = {
                type: 'group',
                label: col.label,
                icon: col.icon,
                prop: col.prop,
                arrow: col.arrow,
                collapse: col.collapse,
                display: col.display
              }
              this.transAvueOptionsToFormDesigner(col).then(res => {
                group.children = res
                data.column.push(group)
              })
            }
            delete data.group
          }
          resolve(data)
        } catch (e) {
          reject(e)
        }
      })
    },
    async getData(type = 'json') {
      if (type === 'string') return beautifier(await this.transformToAvueOptions(this.widgetForm), {
        minify: true
      })
      else return await this.transformToAvueOptions(this.widgetForm)
    }
  }
}
</script>

<style lang="scss">
@use './styles/index';
</style>
